﻿package bakudo.engines.mapEngine.model 
{
	import bakudo.engines.mapEngine.controller.camera.Camera2D;
	import bakudo.engines.mapEngine.global.error.MapError;
	import bakudo.engines.mapEngine.model.actors.ModelActor;
	import bakudo.engines.mapEngine.model.grid.GridCell;
	import bakudo.engines.mapEngine.view.scene.actors.SceneActor;
	import bakudo.utils.misc.TwoDimmPosition;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	/**
	 * ...
	 * @author Bakudo
	 */
	public class WorldModel
	{
		
		
		/** Tablica 2 wymiarowa z danymi plytek z tlem oraz aktorow*/
		public var mapGrid : Array/*of ModelActor*/;
		
		/** Ilosc kolumn z komorkami na mapie */
		public var bgColumnsCount : uint;
		
		/** Ilosc wierszy z komorkami na mapie */
		public var bgRowsCount : uint;
		
		
		
		/** Szerokosc mapy w pixelach */
		public var mapWidth : uint;
		
		/** Wysokosc mapy w pixelach */
		public var mapHeight : uint;
		
		/** Rozmiar komorki w pixelach, szerokosc i wysokosc zarazem */
		public var gridSize : uint;
		
		
		
		/** Szerokosc widoku w pixelach */
		public var viewWidth : uint = 50;
		
		/** Wysokosc widoku w pixelach */
		public var viewHeight : uint = 50;
		
		/** Dystans czyli ile komorek w prawo,gore,lewo,dol wyswietlamy w widoku */
		public var gridViewDistance : uint;
		
		
		
		/** Tablica z elementami typu "Obstacle" */
		public var obstacles : Array/*of ModelActor*/;
		
		/** Aktorzy nie plytki */
		public var characters : Array/*of ModelActor*/;
		
		/** Kolekcja wszystkich niepowtarzajacych sie instancji Bitmap */
		public var viewCollection : Dictionary/*[of String] = of SceneActor */;
		
		/** Pozycja kamery zwiazana z polozeniem x/y w modelu */
		public var cameraPosition : Point;
		
		/** Pozycja indexowa siatki (r,c) na podstawie cameraPosition */
		public var cameraArrayIndex : Point;
		
		
		
		public function WorldModel() 
		{
			init();
		}
		
		
		
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	// PROTECTED METHODS
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	
		protected function init():void {
			cameraPosition = new Point();
			cameraArrayIndex = new Point();
			mapGrid = [];
			viewCollection = new Dictionary();
		}
	
		
		
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	// PUBLIC METHODS
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
		
		/**
		 * Tworzy siatke na tlo mapy w postaci 2 wymiarowej tablicy
		 * Jezeli rCount jest == 0 czyli nie podano parametru to tworzona jest
		 * siatka zblizona do kwadratu
		 * @param gCount - ilosc komorek
		 * @param gSize - rozmiar komorki w siatce
		 * @param cCount - ilosc kolumn w siatce, zawsze musi byc podana
		 * @param rCount - ilosc wierszy w siatce, podawana opcjonalnie
		 */
		public function buildBackgroundGrid(gCount:uint,gSize : uint,cCount:uint,rCount : uint=0):void {
			var i : uint = 0;
			var j : uint = 0;
			bgColumnsCount = cCount;
			bgRowsCount = rCount == 0 ? Math.ceil(gCount/bgColumnsCount) : rCount;
			var cell : GridCell;
			while (i < bgRowsCount) {
				mapGrid.push([]);
				j = 0;
				while (j < bgColumnsCount) {
					cell = new GridCell;
					cell.position.x = j * gridSize;
					cell.position.y = i * gridSize;
					(mapGrid[i] as Array).push(cell);
					j++;
				}
				i++;
			}
			mapWidth = bgColumnsCount * gridSize;
			mapHeight = bgRowsCount * gridSize;
			trace('\n\n\n::::::\tBME - WORLD MODEL - BackgroundGrid created\t::::::');
			trace('\n\tgridCellSize : ' + gSize);
			trace('\n\tgridsCount : ' + gCount);
			trace('\n\tbgRowsCount : ' + bgRowsCount+'\tbgColumnsCount : '+bgColumnsCount );
		}
	
		/**
		 * Dodaje aktorow tla do tablicy wedlug wzorca kolejnosci
		 * rzutujac tablice jednowymiarowa na tablice dwu wymiarowa
		 * @param	mapGrid - tablica z damymi aktorow typu ModelActor
		 * @param	columnsCount - ilosc kolumn na mapie
		 */
		public function addAndSortBackgroundTiles(tiles:Array/*of ModelActor*/):void {
			var row : uint = 0;
			var coll : uint = 0;
			var actor : ModelActor;
			var cell : GridCell;
			while (row < bgRowsCount) {
				mapGrid.push([]);
				coll = 0;
				while (coll < bgColumnsCount) {
					actor = tiles[row * bgColumnsCount + coll] as ModelActor;
					if (!actor) throw new Error(MapError.BAD_INPUT_ACTOR);
					addViewToCollection(actor);
					cell = mapGrid[row][coll] as GridCell;
					cell.backgroundTile = actor;
					actor.position = cell.position.clone();
					coll++;
				}
				row++;
			}
			trace('\n\n\n::::::\tBME - WORLD MODEL - BackgroundGrid is now filled with data\t::::::');
			trace('\n\n\n::::::\tBME - WORLD MODEL - View Collection unique bitmaps has been added\t::::::');
		}
		
		/**
		 * Zwraca widok w postaci bitmapy za pomoca className
		 * @param	cName - nazwa klasy za pomoca, ktorej szukamy widoku
		 * @return	widok zwracany jest za pomoca bitmapDaty
		 */
		public function getViewByClassName(cName:String):BitmapData {
			var actor : SceneActor = viewCollection[cName]; 
			var view : Bitmap = actor.getView(); 
			
			var bd : BitmapData;
			if (view) {
				bd = new BitmapData(view.width, view.height, true, 0x00000000);
				bd.draw(view);
			}
			return bd;
		}
		
		/**
		 * Zwraca obszar zajmowany przez aktora
		 * @param	actor - referencja do aktora, ktorego zajmowany 
		 * obszar obliczamy
		 * @return	obiekt Rectangle zawierajacy obszar
		 */
		public function getActorViewBoundary(actor:bakudo.engines.mapEngine.model.actors.ModelActor):Rectangle{
			var r : Rectangle = new Rectangle();
			r.x = actor.position.x;
			r.y = actor.position.y;
			r.width = gridSize;
			r.height = gridSize;
			return r;
		}
	
		public function addCharacters(characters:Array/*of ModelActor*/):void {
			this.characters = characters;
		}
		
		/**
		 * Dodaje elementy typu obstacle
		 * @param	arr - tablica z elementami typu ModelActor
		 */ 
		public function addObstacles(obstacles:Array/*of ModelActor*/):void {
			var actor : ModelActor;
			var i : uint;
			var n : uint = obstacles.length;
			var obstacle : ModelActor;
			var cell : GridCell;
			var arrPos : TwoDimmPosition;
			while (i < n) {
				obstacle = obstacles[i];
				arrPos = getTwoDimmUnderPoint(obstacle.position);
				cell = getCellByArrayPosition(arrPos);
				if (cell) {
					addViewToCollection(obstacle);
					cell.members.push(obstacle);
				}
				i++;
			}
		}
		
		/**
		 * Zwraca tablice z obiektami typu character znajdujacymi sie w obszarze widoku
		 * @param	cameraPosition
		 * @return - tablica z elementami typu ModelActor
		 */
		public function getCharactersInViewArea(cameraPosition : Point):Array/*of ModelActor*/ {
			var arr : Array/*of ModelActor*/ = [];
			var bounds : Rectangle = new Rectangle(cameraPosition.x, cameraPosition.y, 5 * gridSize, 5 * gridSize);
			bounds.x -= bounds.width * .5;
			bounds.y -= bounds.height * .5;
			var actor : ModelActor;
			for (var i in characters) {
				actor = characters[i] as ModelActor;
				if (bounds.containsPoint(actor.position)) {
					arr.push(actor);
				}
			}
			return arr;
		}
		
		
		
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	// PRIVATE METHODS
	//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
		
		// Jesli instancja widoku nie istnieje to tworzy oraz dodaje widok
		private function addViewToCollection(modelActor:ModelActor):void {
			modelActor.viewClassName = getQualifiedClassName(modelActor.view);
			var viewClassName:String = modelActor.viewClassName;
			if (!viewCollection[viewClassName]) {
				var itemClass : Class;
				itemClass = getDefinitionByName(viewClassName) as Class;
				viewCollection[viewClassName] = new itemClass();
				trace('\n\t\tDodano ' + viewClassName);
			}
		}
		
		// Zwraca komorke na mapie za pomoca pozycji w formacie (row,column)
		private function getCellByArrayPosition(twoDimm:TwoDimmPosition):GridCell {
			if (mapGrid[twoDimm.r]) {
				if (mapGrid[twoDimm.r][twoDimm.c]) {
					return (mapGrid[twoDimm.r][twoDimm.c] as GridCell)
				}
			}
			return null;
		}
		
		// Zwraca pozycje w formacie (row,column) za pomoca pozycji (x,y)
		private function getTwoDimmUnderPoint(point : Point):TwoDimmPosition {
			var p : TwoDimmPosition = new TwoDimmPosition();
			p.r = Math.floor(point.y / gridSize);
			p.c = Math.floor(point.x / gridSize);
			return p;
		}
	}
}